package edu.bu.cs511.p5;

/**
 * This is the main driver class which uses the rest, gluing them
 * together to work towards a task.  When executing outside of the SXE
 * environment, this class contains the main to start and arbitrate
 * execution.  It creates the scheduler hierarchy, populates opcodes,
 * and begins their execution.  It deletes opcodes and schedulers, and
 * ensures that enabling works correctly.<p/>
 *
 * Currently, the hierarchy tested is:<p/>
 *
 * <pre>
 * opcode1[1] opcode2[0] opcode3[0] opcode4 opcode5 opcode6[0.5] opcode7[0.1] opcode8[0.1]
 *      |         |       |          |     |                  |        |        |
 *      FPScheduler(2)[0.5]     RRScheduler(1)[0.2]     ProportionalScheduler(1)[0.3]
 *                  |                   |                |
 *                  +-------ProportionalScheduler(2)-----+
 * </pre/>
 * The values in () are how many threads (max) that scheduler
 * will allow.  The values in [] are the parameters set for
 * that Task within the parent scheduler<p/>
 *
 * This test goes through phases of enabling tasks, deleting them, and
 * reenabling them.
 */
public class Main {
    public static SchedTask schedTask2;

    public static void main(String[] args) {
        /* make the schedulers */
        IScheduler sched1, sched2, sched3, sched4;
        sched1 = new ProportionalScheduler(2);
        sched2 = new FPScheduler(2);
        sched3 = new RRScheduler(1);
        sched4 = new ProportionalScheduler(1);

        /* make the opcode tasks */
        LongOpcode opcode1, opcode2, opcode3, opcode4, opcode5, opcode6, opcode7, opcode8;

        opcode1 = new LongOpcode("1a", 1);
        opcode2 = new LongOpcode("2a", 1);
        opcode3 = new LongOpcode("3a", 1);
        opcode4 = new LongOpcode("4b", 1);
        opcode5 = new LongOpcode("5b", 1);
        opcode6 = new LongOpcode("6c", 1);
        opcode7 = new LongOpcode("7c", 1);
        opcode8 = new LongOpcode("8c", 1);

        SchedTask root, /*schedTask2,*/ schedTask3, schedTask4;
        OpcodeTask one, two, three, four, five, six, seven, eight;

        ThreadManager thdManager;

        /*
	 * The below should create a hierarchy like so:
	 *
	 * opcode1[1] opcode2[0] opcode3[0] opcode4 opcode5 opcode6[0.5] opcode7[0.1] opcode8[0.1]
	 *      |         |       |          |     |                  |        |        |
	 *      FPScheduler(2)[0.5]     RRScheduler(1)[0.2]     ProportionalScheduler(1)[0.3]
	 *                  |                   |                |
	 *                  +-------ProportionalScheduler(2)-----+
	 *
	 * The values in () are how many threads (max) that scheduler
	 * will allow.  The values in [] are the parameters set for
	 * that Task within the parent scheduler
	 *
	 */

        thdManager = SchedHierarchy.createThdManager();

        /* create root */
        root = SchedHierarchy.rootScheduler(thdManager, sched1);

        /* associate them with the correct schedulers */
        schedTask2 = SchedHierarchy.addScheduler(root, sched2, "0.5");
        schedTask3 = SchedHierarchy.addScheduler(root, sched3, "0.2");
        schedTask4 = SchedHierarchy.addScheduler(root, sched4, "0.3");

        root.setName("r");
	schedTask2.setName("fp");
	schedTask3.setName("rr");
	schedTask4.setName("ps");

        /* Easier to understand with inconsistent spacing. */
        if ((one = SchedHierarchy.addOpcode(schedTask2, opcode1, "1")) == null)
            System.out.println("one is null.\n");
        if ((two = SchedHierarchy.addOpcode(schedTask2, opcode2, "0")) == null)
            System.out.println("two is null.\n");
        if ((three = SchedHierarchy.addOpcode(schedTask2, opcode3, "0")) == null)
            System.out.println("three is null.\n");
        if ((four = SchedHierarchy.addOpcode(schedTask3, opcode4)) == null)
            System.out.println("four is null.\n");
        if ((five = SchedHierarchy.addOpcode(schedTask3, opcode5)) == null)
            System.out.println("five is null.\n");
        if ((six = SchedHierarchy.addOpcode(schedTask4, opcode6, "0.5")) == null)
            System.out.println("six is null.\n");
        if ((seven = SchedHierarchy.addOpcode(schedTask4, opcode7, "0.1")) == null)
            System.out.println("seven is null.\n");
        if ((eight = SchedHierarchy.addOpcode(schedTask4, opcode8, "0.1")) == null)
            System.out.println("eight is null.\n");

        one.setName("1");
        two.setName("2");
        three.setName("3");
        four.setName("4");
        five.setName("5");
        six.setName("6");
        seven.setName("7");
        eight.setName("8");

	thdManager.setName("edu.bu.cs511.p5.ThreadManager");
	    
	if (args.length == 1 && args[0].equals("test_allocations")) {
	    one.setInSXE(false);
	    two.setInSXE(false);
	    three.setInSXE(false);
	    four.setInSXE(false);
	    five.setInSXE(false);
	    six.setInSXE(false);
	    seven.setInSXE(false);	    
	    eight.setInSXE(false);

	    one.enableOpcode();
	    two.enableOpcode();
	    three.enableOpcode();
	    four.enableOpcode();
	    five.enableOpcode();
	    six.enableOpcode();
	    seven.enableOpcode();	    
	    eight.enableOpcode();
	} else {
	    System.out.println("The scheduler hierarchy looks like so:");
	    System.out.println("\nopcode1[1] opcode2[0] opcode3[0] opcode4 opcode5 opcode6[0.5] opcode7[0.1] opcode8[0.1]\n"+
			       "       |         |       |            |     |                |        |        |\n"+
			       "       FPScheduler(2)[0.5]     RRScheduler(1)[0.2]     ProportionalScheduler(1)[0.3]\n"+
			       "                  |                   |                |\n"+
			       "                  +-------ProportionalScheduler(2)-----+");
	    System.out.println("\nThe values in () are how many threads (max) that scheduler\n"+
			       "will allow.  The values in [] are the parameters set for\n"+
			       "that Task within the parent scheduler.\n");
	    System.out.println("Tasks are denoted as explained in TestTaskPercenages.java, by a pair of characters:\n"+
			       "First a number for the task number (opcode1 = 1), and then a character a-c where the\n"+
			       " FPScheduler = a, RRScheduler = b, and ProportionalScheduler = c.");
	    System.out.println("\nNOTE: please enter \"test_allocations\" on the command line if you wish to just\n"+
			       "test to make sure that tasks are receiving correct CPU allocations (use in \n"+
			       "conjunction with TestTaskPercentages.java).\n");

	    Enabler enabler = new Enabler();
	    enabler.addOpcode(one);
	    enabler.addOpcode(two);
	    enabler.addOpcode(three);
	    enabler.addOpcode(four);
	    enabler.addOpcode(five);
	    enabler.addOpcode(six);
	    enabler.addOpcode(seven);
	    enabler.addOpcode(eight);
	    enabler.start();
	}
        thdManager.executeTask();

        System.out.println();
    }

    /**
     * The Enabler simply exists to enable opcodes.  This is used to
     * test 1) that having no enabled opcodes at the start doesn't
     * interfere with the functionality of ThreadManager and 2) to
     * test the enabling logic of the opcodes and schedulers,
     * ie. ensure that even if tasks aren't enabled immediately, they
     * will still function correctly when enabled later.
     */
    public static class Enabler extends Thread {
        private final int maxOps = 32;
        private OpcodeTask[] ops;
        private int opCount = 0;

	/**
	 * Constructor for the enabler which will initialize all
	 * private data structures.
	 */
        public Enabler() {
            ops = new OpcodeTask[maxOps];
        }

	/**
	 * Add an opcode to later be enabled.
	 * <p/>
	 * @REQUIRES op is an opcode to be enabled later.
	 */
        public void addOpcode(OpcodeTask op) {
            ops[opCount] = op;
            opCount++;
        }

        /**
	 * Simply go through the list of opcodes that we are to enable
	 * (passed in to addOpcode), and enable them one by one.
	 * Check multiple code paths by enabling them again, deleting
	 * some, and reenabling them.<p/>
	 *
         * This method will include sleeps that simply check the code
         * path in the edu.bu.cs511.p5.ThreadManager to make sure it
         * blocks correctly till tasks are enabled.
	 *
	 * @MODIFIES go through a progression of enabling, and
	 * deleting tasks to test code-paths.
         */
        public void run() {
            try {
                sleep(100);
            } catch (InterruptedException ie) {
            }

	    /* enable once! */
	    System.out.println("Testing task enabling: Enabling all the tasks for the first time.");
            for (int i = 0; i < opCount; i++) {
                ops[i].enableOpcode();
            }

            try {
                sleep(500);
            } catch (InterruptedException ie) {
            }

	    /*
	     * enable twice!  This will test enabling WHILE tasks are
	     * on the runningQueue.
	     */
	    System.out.println("\nTesting repeated enabling of tasks: Enabling all tasks a second time.");
            for (int i = 0; i < opCount; i++) {
                ops[i].enableOpcode();
            }

            try {
                sleep(500);
            } catch (InterruptedException ie) {
            }

	    /* remove some tasks! */
	    System.out.println("\nTesting task removal: Removing tasks 1-2.");
            ops[0].endTask();
            ops[1].endTask();
            //ops[2].endTask();
	    //schedTask2.endTask();

            try {
                sleep(500);
            } catch (InterruptedException ie) {
            }

	    /*
	     * and enable again! This will test enabling tasks that
	     * are on the waitingQueue, and also enabling tasks that
	     * should not be in scheduling hierarchy.
	     */
	    System.out.println("Testing removal (cont..): Again enabling all tasks (which should not include 1-2).");
	    for (int i = 0; i < opCount; i++) {
                ops[i].enableOpcode();
            }

            try {
                sleep(500);
            } catch (InterruptedException ie) {
            }

	    /*
	     * Recreating opcodes 1-3.
	     */
	    System.out.println("\nTesting reinsertion and reexecution of tasks: Recreating tasks 1-2 and enabling all.");
	    ops[0] = SchedHierarchy.addOpcode(schedTask2,  new LongOpcode("1a", 1), "1");
	    ops[1] = SchedHierarchy.addOpcode(schedTask2,  new LongOpcode("2a", 1), "0");
	    //ops[2] = SchedHierarchy.addOpcode(schedTask2,  new LongOpcode("3a", 1), "0");

            try {
                sleep(500);
            } catch (InterruptedException ie) {
            }

	    for (int i = 0; i < opCount; i++) {
                ops[i].enableOpcode();
            }

            try {
                sleep(500);
            } catch (InterruptedException ie) {
            }

	    System.out.println("\nNow setting to non-blocking execution mode to test CPU bound correctness.  Press ^C to quit.");
	    
	    for (int i = 0; i < opCount; i++) {
                ops[i].setInSXE(false);
		ops[i].enableOpcode();
            }
	    
        }
    }
}
